/*
 * ota.c
 *
 *  Created on: May 24, 2020
 *      Author: daniel
 */

#include <string.h>
#include "OTA/ota.h"
#include "OTA/common.h"
#include "OTA/flash_if.h"
#include "OTA/ymodem.h"
#include "main.h"
#include "md5.h"

#include "version.h"

bool OTA_Erase_Bank2();

// 获取当前运行到Bank
ACTIVE_BANK GetCurrentActiveBank()
{
	FLASH_AdvOBProgramInitTypeDef OBConfig;

	HAL_FLASHEx_AdvOBGetConfig( &OBConfig );

	if ( OBConfig.BootConfig == OB_BOOT_BANK1 )
	{
		return ACTIVE_BANK1;
	}
	else
	{
		// Boot from Bank2 active 检查当前是否是bank2。如果不是，应该有问题，需要恢复到bank1
		return (SYSCFG->CFGR1 & SYSCFG_CFGR1_UFB) ? ACTIVE_BANK2 : ACTIVE_BANK1;
	}
}


/**
  * @brief  Modify the BFB2 status of user flash area.
  * @param  none
  * @retval HAL_StatusTypeDef HAL_OK if change is applied.
  */
HAL_StatusTypeDef BankSwitch(void)
{
  FLASH_AdvOBProgramInitTypeDef adv_config;
  HAL_StatusTypeDef result;

  /* Get the current configuration */
  HAL_FLASHEx_AdvOBGetConfig( &adv_config );

  if ( adv_config.BootConfig == OB_BOOT_BANK1 ) /* BANK1 active for boot */
  {
    adv_config.BootConfig = OB_BOOT_BANK2;
  }
  else
  {
    adv_config.BootConfig = OB_BOOT_BANK1;
  }

  /* Initiating the modifications */
  result = HAL_FLASH_OB_Unlock();

  /* program if unlock is successful */
  if (result == HAL_OK)
  {
    result = HAL_FLASHEx_AdvOBProgram(&adv_config);
  }
  if (result == HAL_OK)
  {
    HAL_FLASH_OB_Launch();
  }
  return result;
}


// 从bank2拷贝到bank1，并检查拷贝结果，然后切换会bank1引导
void CopySwitch()
{
	HAL_StatusTypeDef 		status;
	uint16_t				ota_length;
	uint32_t				ota_ver;
	int						i, j, pos;
	uint32_t 				buffer[WORDS_IN_HALF_PAGE];

	ota_ver = *(uint32_t*)EEPROM_BANK1_VER;
	ota_length = *(uint16_t*)EEPROM_BANK1_LEN;

	// 启动到Bank2时，原有Bank1地址就变成Bank2的地址
	OTA_Erase_Bank2();

	// Flash
	// 写入数据到flash
	status = HAL_FLASH_Unlock();

	/* Clear all FLASH flags */
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_SIZERR |
						   FLASH_FLAG_OPTVERR | FLASH_FLAG_RDERR | FLASH_FLAG_FWWERR |
						   FLASH_FLAG_NOTZEROERR);

	for (i = 0; i < FLASH_BANK_SIZE / (FLASH_PAGE_SIZE / 2); i++) {
		pos = i * FLASH_HALF_PAGE_SIZE;
		for (j = 0; j < WORDS_IN_HALF_PAGE; j++) {
			buffer[j] = *(uint32_t *)(FLASH_START_BANK1 + 4 * j + pos);
		}
		status = HAL_FLASHEx_HalfPageProgram(FLASH_START_BANK2 + pos, buffer);
	}
//	if (status != HAL_OK) {
//		return;
//	}

	status = HAL_FLASH_Lock();
//	if (status != HAL_OK) {
//		return;
//	}

	// EEPROM
	status = HAL_FLASHEx_DATAEEPROM_Unlock();
//	if (status != HAL_OK) {
//		return;
//	}

	while (1) {
		// ver
		HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, EEPROM_BANK2_VER, ota_ver);

		// length
		HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_HALFWORD, EEPROM_BANK2_LEN, ota_length);

		// tag
		for (i = 0; i < 16; i++){
			uint32_t	tagByte;
			tagByte = *(uint8_t*)(EEPROM_BANK1_TAG + i);
			status = HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, EEPROM_BANK2_TAG + i, tagByte);
			if (status != HAL_OK) {
				break;
			}
		}

		break;
	}

	HAL_FLASHEx_DATAEEPROM_Lock();

	// 拷贝成功，关闭BFB2，重启
	BankSwitch();

    HAL_NVIC_SystemReset();

    return;
}

// 判断当前是否处于bank2，如果是，则进行拷贝切换
void OTA_switch() {
	ACTIVE_BANK bank;

	bank = GetCurrentActiveBank();

	// 判断当前是否在bank2运行，如果是，则进入copy to bank1的过程
	if (bank == ACTIVE_BANK2) {
		CopySwitch();
	}
}

static uint32_t		firmware_ver_bank2 		= 0;
static uint16_t		firmware_length_bank2 	= 0;
static int16_t		ota_pos 				= -1;		// 第几个64字节

// 无论是在OTA，还是在CopySwitch时，都是擦除Bank2
bool OTA_Erase_Bank2()
{
	HAL_StatusTypeDef 		status;
	FLASH_EraseInitTypeDef 	desc;
	uint32_t 				PageError;
	bool					need_erase = false;
	int						i;
	bool					ret = true;

	status = HAL_FLASH_Unlock();

	/* Clear all FLASH flags */
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_SIZERR |
						   FLASH_FLAG_OPTVERR | FLASH_FLAG_RDERR | FLASH_FLAG_FWWERR |
						   FLASH_FLAG_NOTZEROERR);

	if (status != HAL_OK) {
		return false;
	}

	// 检查BANK2是否全空白（避免重复擦除）
	// 因为L0的Flash是EEPROM构造，所以擦除后，都是0
	for (i = 0; i < FLASH_BANK_SIZE; i++) {
		if (*(uint8_t*)(FLASH_START_BANK2 + i) != 0) {
			need_erase = true;
			break;
		}
	}

	if (need_erase) {
		desc.TypeErase = FLASH_TYPEERASE_PAGES;
		desc.PageAddress = FLASH_START_BANK2;
		desc.NbPages = FLASH_BANK_SIZE / FLASH_PAGE_SIZE;
		if (HAL_FLASHEx_Erase(&desc, &PageError) != HAL_OK) {
			ret = false;
		}
	}

	status = HAL_FLASH_Lock();
	if (status != HAL_OK) {
		return false;
	}

	return ret;
}

bool OTA_begin(FIRMWARE_INFO* info, bool* same)
{
	HAL_StatusTypeDef 		status;
	bool					ret = true;
	uint16_t				current_length;
	uint8_t*				current_tag;
	int						i;

	current_length = *(uint16_t*)EEPROM_BANK1_LEN;
	current_tag = (uint8_t*)EEPROM_BANK1_TAG;

	// 和当前运行版本信息比较，如果3个都完全相同，则不需要升级
	if ( (current_version[1] == info->firmwareVer)
			&& (current_length == info->firmwareSize)
			&& (memcmp(current_tag, info->tag, 16) == 0) ) {
		*same = true;
		return true;
	}

	if ( (info->firmwareVer == firmware_ver_bank2)
		&& (info->firmwareSize == firmware_length_bank2)
		&& (memcmp(info->tag, (uint8_t*)EEPROM_BANK2_TAG, 16) == 0)
		&& (ota_pos >= 0)) {
		*same = false;
		return true;
	}

	// 还要判断是不是数据相同，但是pos还没有达到合理数字（继续下载，而不进行整体擦除）

	// 擦除bank 2 flash
	ret = OTA_Erase_Bank2();
	if (!ret) {
		return false;
	}


	// EEPROM写入新数据（不需要特别擦除，直接重写即可，MCU会自动判断）
	status = HAL_FLASHEx_DATAEEPROM_Unlock();
	if (status != HAL_OK) {
		return false;
	}

	while (1) {
		// ver
		status = HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, EEPROM_BANK2_VER, info->firmwareVer);
		if (status != HAL_OK) {
			ret = false;
			break;
		}

		// length
		status = HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_HALFWORD, EEPROM_BANK2_LEN, info->firmwareSize);
		if (status != HAL_OK) {
			ret = false;
			break;
		}

		// tag
		for (i = 0; i < 16; i++){
			status = HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, EEPROM_BANK2_TAG + i, info->tag[i]);
			if (status != HAL_OK) {
				ret = false;
				break;
			}
		}

		break;
	}

	status = HAL_FLASHEx_DATAEEPROM_Lock();
	if (status != HAL_OK) {
		return ret;
	}

	if (ret) {
		// 保存当前写入的信息
		firmware_ver_bank2 = info->firmwareVer;
		firmware_length_bank2 = info->firmwareSize;
		ota_pos = 0;
	}

	return ret;
}

// data缓冲区要以64字节长度（不足的，后面要填充0）
bool OTA_data(uint16_t pos, uint8_t* data, uint8_t length, bool* lastPack)
{
	HAL_StatusTypeDef 		status;
	bool					ret = true;

	*lastPack = false;

	// 写入数据到flash
	status = HAL_FLASH_Unlock();

	/* Clear all FLASH flags */
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_SIZERR |
						   FLASH_FLAG_OPTVERR | FLASH_FLAG_RDERR | FLASH_FLAG_FWWERR |
						   FLASH_FLAG_NOTZEROERR);

	if (status != HAL_OK) {
		return false;
	}

	status = HAL_FLASHEx_HalfPageProgram(FLASH_START_BANK2 + (pos * LORA_OTA_PACKET_SIZE), (uint32_t*)data);
	if (status != HAL_OK) {
		ret = false;
	}

	status = HAL_FLASH_Lock();
	if (status != HAL_OK) {
		ret = false;
	}

	if (ret) {
		// 判断是否最后一包
		uint16_t	len;
		len = pos * LORA_OTA_PACKET_SIZE + length;

		if ( len ==  firmware_length_bank2) {
			// 做最后验证
			ret = OTA_end();
			*lastPack = true;
		} else if (len < firmware_length_bank2) {
			// 更新pos
			ota_pos++;
		} else {
			ret = false;
		}
	}

	return ret;
}

#define MD5_UPDATE_LEN		64

bool OTA_end()
{
	MD5_CTX		md5_c;
	int			len;
	uint8_t*	data;

	MD5Init(&md5_c);

	// 检查Bank2 MD5
	len = firmware_length_bank2;
	data = (uint8_t*)FLASH_START_BANK2;

	while (len > 0) {
		if (len > MD5_UPDATE_LEN) {
			MD5Update(&md5_c, data, MD5_UPDATE_LEN);
		} else {
			MD5Update(&md5_c, data, len);
		}

		len -= MD5_UPDATE_LEN;
		data += MD5_UPDATE_LEN;
	}

	MD5Final(&md5_c);

	// 和EEPROM进行比对
	if (memcmp(md5_c.digest, (void*)EEPROM_BANK2_TAG, 16) != 0) {
		return false;
	}

	// 成功后，切换bank启动
    if (BankSwitch() != HAL_OK)
    {
    	return false;
    }

    HAL_NVIC_SystemReset();

	// 正常是无法跑到这里的
	return true;
}

uint16_t GetOTAPos()
{
	if (ota_pos < 0) {
		return 0;
	} else {
		return ota_pos;
	}
}

void OTA_reset()
{
	firmware_length_bank2 = 0;
	firmware_ver_bank2 = 0;
	ota_pos = -1;
}
